// use jquery with double dollars
var $$ = jQuery;

/**
 * Online
 * The main online object
 *
 * @author Seth
 * @version 0.1
 */
var Online = function() {
	var store = {}

	// cookie save
	, _cs = function(key, object) {
		return $$.cookie(key, JSON.stringify($$.extend({}, _cg(key), object)), { path: '/', expires: 1 });

	// cookie get
	}, _cg = function(key) {
		return JSON.parse($$.cookie(key));

	// ajax extnension
	}, _aj = function(o) {
		$$.ajax($$.extend({}, o, {
			url: SITE_URL + o.url,
			success: function(x) {
				if(x.success)
					o.success(x);
			}
		}));

	// constructor
	}, __construct = function() {
		// get resources async
		Online.aj({
			url: 'site/ape',
			type: 'post',
			data: {},
			cache: false,
			success: function (x) {
				// Add buddy list
				Online.Data.focus = false;
				Online.Data.me = x.me;
				Online.Data.buddies = x.buddies;
				Online.Data.providers = x.providers;
				Online.Data.offline = x.offline;

				// Add Session.js to APE JSF to handle multitab and page refresh
				APE.Config.scripts.push(APE.Config.baseUrl + '/Source/Core/Session.js');

				// Initialize APE_Client
				Online.Data.ape = new Online.APE({
					name: x.me.id,
					status: x.status,
					shadow: x.shadow
				});
				Online.Data.ape.load({
					identifier: 'online',
					channel: 'online'
				});

				// init side and stealth
				Online.Sidebar.init();
				Online.Stealth.init();

				// see / check focus
				window.setInterval(function() {
					// window has focus?
					jQuery(window).focus(function() {
						Online.Data.focus = true;
					}).blur(function() {
						Online.Data.focus = false;
					});
				}, 1000);
			}
		});
	};

	// public, yay
	return {
		init: __construct,
		cs: _cs,
		cg: _cg,
		aj: _aj
	};
}();

/**
 * Online Sidebar
 * The sidebar part for online
 *
 * @author Seth
 * @version 0.1
 */
Online.Sidebar = function() {
	var refresh = false,
		store = {
		defaults: {
			sidebar: false,
			sounds: true
		}
	}, sys = $$('#sidebar', '#online'),
	dock = $$('#dock', '#online')

	// bind events here
	, bind = function() {
		var i = $$('#open_close', sys).find('img'),
			s = $$('a.sounds', sys).find('img');

		// set cookie defaults
		if(Online.cg('ifm_online') == null)
			Online.cs('ifm_online', store.defaults);

		// remember states
		var r = Online.cg('ifm_online');

		// sidebar state
		if(r.sidebar) __open();
		else __close();

		// sound state
		if(r.sounds) __unmute();
		else __mute();

		// open function
		function __open() {
			i.attr('src', '/images/online/slider-close.png');
			sys.removeClass('sidebar_closed').addClass('sidebar_open', 100);
			dock.removeClass('dock_closed').addClass('dock_open', 100);
			Online.cs('ifm_online', {sidebar: true});
		};

		// close function
		function __close() {
			i.attr('src', '/images/online/slider-open.png');
			sys.removeClass('sidebar_open').addClass('sidebar_closed', 100);
			dock.removeClass('dock_open').addClass('dock_closed', 100);
			Online.cs('ifm_online', {sidebar: false});
		};

		// mute function
		function __mute() {
			s.attr('src', s.attr('src').replace('-on', '-off'));
			Online.cs('ifm_online', {sounds: false});
		}

		// unmute function
		function __unmute() {
			s.attr('src', s.attr('src').replace('-off', '-on'));
			Online.cs('ifm_online', {sounds: true});
		}

		// button event
		$$('#open_close', sys).click(function() {
			if(/open/g.test(i.attr('src'))) __open();
			else __close();
		});

		// bind status
		sys.delegate('a.status', 'click', Online.Stealth.context);

		// bind sounds
		sys.delegate('a.sounds', 'click', function(){
			if(/\-on/g.test(s.attr('src'))) __mute();
			else __unmute();
		});

		// search list focus
		$$('#sidebar_search input', sys).focus(function() {
			var f = $$(this, sys);
			if(f.val() == translate('search')) f.val('');
		});

		// search list blur
		$$('#sidebar_search input', sys).blur(function() {
			var f = $$(this, sys);
			if(f.val() == '') f.val(translate('search'));
		});

		// filter buddies
		function _search() {
			// get value
			var v = $$.trim($$(this, sys).val());

			// same value as before? do nothing
			if(store.filter == v)
				return;

			// rewrite list and apply the filter
			_writeList(Online.Data().filter(v), v);

			// save what we searched for
			store.filter = v;
		};

		// attach on key events
		$$('#sidebar_search input', sys).bind('keydown', $$.debounce(200, _search));

		// bind on buddy click
		sys.delegate('div.user', 'click', function(e){
			var t = $$(e.target).hasClass('profile');

			// profile click?
			if(t) {
				Frame.go($$(e.target).attr("href"));
				return false;
			}

			// open chat
			Online.Dock.open($$(e.target).closest('table').attr('class'), 1);
		});

		// refresh buddy list or not
		setInterval(function() {
		    if (refresh) {
		        refresh = false;

				var v = $$.trim($$('#sidebar_search input', sys).val());
				if(v != translate('search'))
					_writeList(Online.Data().filter(v), v);
				else
					_writeList();
		    }
		}, 200);

	// populate the buddy list
	}, _writeList = function(filtered, val) {
		// decide on data
		var data = filtered ? filtered : Online.Data.buddies;

		// split data based on online or not
		var online = [];
		var offline = [];
		$$.each(data, function(k, v){
			if(v.status == 'online')
				online.push(v);
			else
				offline.push(v);
		});

		// sorting function
		function __sort(a, b){
			var nameA = a.name.toLowerCase(), nameB = b.name.toLowerCase();
			if (nameA < nameB) return -1 ;
			if (nameA > nameB) return 1;
			return 0;
		};

		// sort buddies
		online.sort(__sort);
		offline.sort(__sort);

		// remove loading or messages
		$$('#sidebar_users div.inside div.user, #sidebar_users div.inside span.msg', sys).remove();

		// fill the buddy list
		$$.each(online.concat(offline), function(k, v){
			$$('#sidebar_users div.inside', sys).append(
				'<div class="user">'+
					'<table class="user_'+ v.id +'">'+
					'<col width="1%"><col><col width="1%">'+
					'<tr>'+
						'<td valign="top" style="vertical-align: top;">'+
							'<div class="avatar"><img src="'+ v.avatar +'"></div>'+
						'</td>'+
						'<td>'+
							'<a style="font-weight: bold;" href="javascript:void(0);">'+ v.name +'</a>'+
							'<div class="desc"><span>'+ v.type +' - <a class="profile" style="font-size: 11px;" href="'+ v.url +'">'+ translate('profile') +'</a></span></div>'+
						'</td>'+
						'<td><div class="'+ v.status +'"></div></td>'+
					'</tr>'+
					'</table>'+
				'</div>');
		});

		// so what happend?
		if(!data.length > 0) {
			if(!$$('#sidebar_users div.inside span.msg', sys).length > 0)
				$$('#sidebar_users div.inside', sys).append('<span class="msg">'+
					(filtered && val ? translate('no_filtered').replace('%s', val) : translate('no_buddies'))+
				'</span>');
		}

	// refresh buddy list
	}, _update = function() {
		refresh = true;

	// constructor
	}, __construct = function() {
		bind();
		_writeList();

		// add user scroll bar
		$$('#sidebar_users', sys).mCustomScrollbar({
			scrollInertia: 0,
			advanced: {
				updateOnContentResize: true
			}
		});

	// translate
	}, translate = function(key) {
		return Online.Translate.init(key);
	};

	// public, yay
	return {
		init: __construct,
		update: _update
	};
}();

/**
 * Online Dock
 * The chat part for online
 *
 * @author Seth
 * @version 0.1
 */
Online.Dock = function() {
	var store = {},
		sys = $$('#dock', '#online')

	// show box
	, show = function(what, created) {
		// minimize all other boxes
		$$('div.box', sys).each(function(k, v){
			var it = $$(v);

			// only close the other windows
			if(what.attr('id') != it.attr('id'))
				it.removeClass('open', 100);
		});

		// focus window
		what.removeClass('hidden');

		// already opened?
		if(what.hasClass('open')) {
			what.find('input').focus();
			return false;
		}

		// mark as read
		Online.Data().mark(what.attr('id').replace('box_', ''));

		// open
		what.addClass('open', 100, function() {
			what.find('input').focus();

			// box just created? load history
			if(created)
				what.find('a.history_1').click();

			// update scroll and scroll to bottom
			what.find('div.body').mCustomScrollbar("update");
			what.find('div.body').mCustomScrollbar("scrollTo", 'bottom');

			// stop blinking
			stop(what);

			// save state
			save();
		});

	// new message
	}, blink = function(what) {
		// open box?
		if(what.hasClass('open'))
			return false;

		// hidden?
		if(what.hasClass('hidden'))
			what.removeClass('hidden');

		// already blinking?
		if(what.hasClass('blink'))
			return false;

		// blink
		what.addClass('blink');

		// save state
		save();

	// hide box
	}, hide = function(what, f) {
		if(!f) f = function() {
			save();
		};

		what.removeClass('open', 100, f);

	// close box
	}, close = function(what) {
		hide(what, function() {
			what.addClass('hidden');
			save();
		});
	// open box
	}, open = function(user, s) {
		var created = false,
			u = user.replace('user_', '')
			b = user.replace('user_', 'box_');

		// doesn't exist ? create it
		if(!$$('#'+ b, sys).length > 0) {
			created = true;
			_create(u, b, s == 2 ? true : false);
		}

		// identity
		var id = $$('#'+ b, sys);

		// open expanded / just blink
		if(s == 1) show(id, created);
		else if(s == 2) blink(id);

	// create box
	}, _create = function(u, b, s) {
		var user = Online.Data().find(u);
		sys.prepend(
			'<div id="'+ b +'" class="box">'+
				'<div class="title'+ (s ? ' blink' : '') +'">'+
					'<div class="name">'+ user.name +'</div>'+
					'<div class="buttons">'+
						'<span class="stealth"></span>'+
						'<span class="close"></span>'+
						'<div class="clear"></div>'+
					'</div>'+
					'<div class="clear"></div>'+
				'</div>'+
				'<div class="body">'+
					'<div class="inside">'+
						'<div class="history"><span>'+
							'<a href="javascript:void(0);" class="history_1">' + translate('yesterday') + '</a>'+
							' &bull; '+
							'<a href="javascript:void(0);" class="history_7">' + translate('7 days') + '</a>'+
							' &bull; '+
							'<a href="javascript:void(0);" class="history_30">' + translate('1 month') + '</a>'+
							' &bull; '+
							'<a href="javascript:void(0);" class="history_90">' + translate('3 months') + '</a>'+
						'</span></div>'+
					'</div>'+
				'</div>'+
				'<div class="input">'+
					'<input type="text" value="" />'+
				'</div>'+
			'</div>'
		);

		// add scroll
		$$('#'+ b, sys).find('div.body').mCustomScrollbar({
			scrollInertia: 0
		});

	// get unread messages
	}, unread = function() {
		// no offline data? exit here
		if(!Online.Data.offline.length > 0)
			return false;

		// go through each offline messages
		$$.each(Online.Data.offline, function(k, v){
			open('user_'+ v, 2);
			window.setTimeout(function() {
				$$('#box_' + v, sys).find('a.history_90').click();
			}, 1);
		});

	// read state
	}, state = function() {
		// get state
		var state = Online.cg('ifm_online').state || {};

		// go through each saved state
		$$.each(state, function(k, v){
			open('user_'+ k, v);

			// blinking or closed?
			if(v == 2 || v == 3) {
				window.setTimeout(function() {
					$$('#box_' + k, sys).find('a.history_1').click();
				}, 1);
			}
		});

	// save state
	}, save = function() {
		// get state
		var state = {};

		// go through each box and determine if we have to save or not
		$$('div.box', sys).each(function(k, v){
			var it = $$(v),
				st = 3;

			// set state
			if(it.hasClass('open')) st = 1;
			else if(it.hasClass('blink')) st = 2;

			// save state
			if(!it.hasClass('hidden'))
				state[it.attr('id').replace('box_', '')] = st;
		});

		// save state
		Online.cs('ifm_online', {state: state});

	// bind events
	}, bind = function() {
		// minimize / maximize box
		sys.delegate('div.box > div.title', 'click', function(e) {
			var p = $$(this).closest('div.box');

			// show or hide?
			if(p.hasClass('open')) hide(p);
			else show(p);
		});

		// stealth box
		sys.delegate('div.box > div.title > div.buttons > span.stealth', 'click', function(e) {
			// hide from this user
			Online.Stealth.hide($$(this).closest('div.box').attr('id').replace('box_', ''));

			// stop bubble
			return false;
		});

		// close box
		sys.delegate('div.box > div.title > div.buttons > span.close', 'click', function(e) {
			close($$(this).closest('div.box'));

			// stop bubble
			return false;
		});

	// play animations for blink
	}, play = function() {
		// go through each
		$$('div.blink', sys).each(function(k, v){
			$$(v, sys).find('div.title').toggleClass('blink', 500);
		});

		// loop this
		window.setTimeout(play, 1000);

	// stop blinking
	}, stop = function(what) {
		what.find('div.title').removeClass('blink', 500);
		what.removeClass('blink');

	// do construct
	}, __construct = function() {
		sys.removeClass('dock_hidden');
		unread();
		state();
		bind();
		play();

	// translate
	}, translate = function(key) {
		return Online.Translate.init(key);
	};

	// public, yay
	return {
		init: __construct,
		open: open
	};
}();

/**
 * Online Translate
 * Get translate string from dom
 *
 * @author Seth
 * @version 0.1
 */
Online.Translate = function() {
	var cache = {},
		sys = $$('#online')

	// translation handle
	_load = function() {
		var t = $$('#translate', sys);
		cache = $$.parseJSON(t.html());

	// constructor
	}, __construct = function(i) {
		if($$.isEmptyObject(cache))
			_load();

		return cache[i] == undefined ? '['+i+']' : cache[i];
	};

	// public
	return {
		init: __construct
	};
}();

/**
 * Online Data
 * Here we will store all of the online data
 *
 * @author Seth
 * @version 0.1
 */
Online.Data = function() {
	// find user
	var _find = function(val) {
		// found in me?
		if(Online.Data.me.id == val)
			return Online.Data.me;

		// go through each buddy
		var data = {};
		$$.each(Online.Data.buddies, function(k, v){
			if(v.id == val) {
				data = v;
				return false;
			}
		});

		// nothing found? FUCK, search the providers
		if($$.isEmptyObject(data))
			$$.each(Online.Data.providers, function(k, v){
				if(v.id == val) {
					data = v;
					return false;
				}
			});

		// return what we found
		return data;

	// filter users
	}, _filter = function(val) {
		// no value? return unfiltered buddy list
		if(val == '')
			return Online.Data.buddies;

		// filter buddy list
		var data = [];
		$$.each(Online.Data.buddies, function(k, v){
			// LIKE "%value%"
			if(v.name.toLowerCase().indexOf(val.toLowerCase()) > -1)
				data.push(v);
		});

		// return what we found
		return data;

	// mark as read
	}, _mark = function(val) {
		var from;
		$$.each(Online.Data.offline, function(k, v){
			if(v == val) {
				from = v;
				Online.Data.offline.splice(k, 1);
				return false;
			}
		});

		// mark as read
		if(from) {
			Online.aj({
				url: 'site/mark',
				type: 'post',
				data: {from: from},
				cache: false,
				success: function(x) {
					// nothing here
				}
			});
		}
	};

	// public, yay
	return {
		find: _find,
		filter: _filter,
		mark: _mark
	};
};

/**
 * Online Stealth
 * The stealth part of the online class
 *
 * @author Seth
 * @version 0.1
 */
Online.Stealth = function() {
	var store = {},
		sys = $$('#sidebar', '#online'),
		lock = true

	// context menu
	, _context = function(e) {
		// show the context menu
		window.setTimeout(function(){
			// don't spam.
			if(lock)
				return false;

			$$('#sidebar_context', sys).show();
		}, 1);

	}, _msg = function(msg) {
		$$('<div id="dialog-info" title="'+ translate('message') +'" class="ui-state-highlight">'+
			'<div style="margin: 8px 0px 2px 0px; line-height: 18px;">'+
				'<table cellpadding="0" cellspacing="0" border="0"><tr><td valign="top" style="padding: 1px 5px 0px 0px;"><span class="ui-icon ui-icon-info"></span></td>'+
				'<td>' + msg + '</td></tr></table>'+
			'</div>'+
		'</div>').dialog({
			resizable: false,
			modal: true,
			width: 350,
			minHeight: 50,
			buttons: [{
				text: translate('ok'),
				click: function() {
					$$(this).dialog("close");
				}
			}],
			close: function() {
				$$("#dialog-info").remove();
			}
		});

	// hide from whom?
	}, _hide = function(from) {
		// get some vars going
		var shadow = JSON.parse(decodeURIComponent(Online.Data.shadow)),
			user = Online.Data().find(from),
			e = $$.inArray(from, shadow)

		// show message accordingly
		if(e == -1) _msg(translate('appear_offline').replace('%s', user.name));
		else _msg(translate('appear_online').replace('%s', user.name));

		// set shadow
		Online.Data.ape.pipe.request.send('update_shadow', {
			'id': from
		});

		// update in db
		Online.aj({
			url: 'site/shadow',
			type: 'post',
			data: {id: from},
			cache: false,
			success: function(x) {
				// nothing here
			}
		});

	// unlock button
	}, _unlock = function() {
		lock = false;

	// bild events here
	}, bind = function() {
		// anywhere you click, hide the context menu
		$$('body').click(function() {
			$$('#sidebar_context', sys).hide();
		});

		// update the status on the server
		function __updateStatus(x) {
			// set status
			Online.Data.ape.pipe.request.send('update_status', {
				'name': x
			});

			// update in db as well
			Online.aj({
				url: 'site/status',
				type: 'post',
				data: {name: x},
				cache: false,
				success: function(x) {
					// nothing here
				}
			});
		};

		// change status (online/offline(invisible))
		$$('#ctx_status', sys).click(function() {
			var s = $$(this, sys);

			// go offline/invisible
			if(s.attr('class') == 'offline') {
				// switch button
				s.removeClass().addClass('online');
				s.html(translate('go_online'));

				// mark as offline
				$$('#sidebar_infos', sys).find('span').removeClass().addClass('offline');
				$$('#sidebar_infos', sys).find('span').html(
					'<img src="/images/online/status-off.png" />'+ translate('invisible')
				);

				// set to offline
				__updateStatus('offline');

			// go online
			} else {
				// switch button
				s.removeClass().addClass('offline');
				s.html(translate('go_offline'));

				// mark as online
				$$('#sidebar_infos', sys).find('span').removeClass().addClass('online');
				$$('#sidebar_infos', sys).find('span').html(
					'<img src="/images/online/status-on.png" />'+ translate('online')
				);

				// set to online
				__updateStatus('online');
			}
		});

		// invisible list
		$$('#ctx_list', sys).click(function() {
			// get shadow list
			var shadow = JSON.parse(decodeURIComponent(Online.Data.shadow)),
				list = '';

			// compile list
			$$.each(shadow, function(k, v){
				list += Online.Data().find(v).name +', ';
			});

			// if list is empty add nobody :P
			if(!list.length > 0)
				list = translate('nobody')+ ', ';

			// show message
			_msg(translate('invisible_list').replace('%s', list.substring(0, list.length - 2)));
		});

	// the constructor
	}, __construct = function() {
		bind();

	// translate
	}, translate = function(key) {
		return Online.Translate.init(key);
	};

	// public, yay
	return {
		init: __construct,
		context: _context,
		unlock: _unlock,
		hide: _hide
	}
}();

/**
 * Online APE
 * The ape part of the online class
 *
 * @author Seth
 * @version 0.1
 */
Online.APE = new Class({
	Extends: APE.Client,
	Implements: Options,
	options: {},

	// constructor function
	initialize: function(options) {
		this.setOptions(options);
		this.sidebar = $$('#sidebar', '#online');
		this.dock = $$('#dock', '#online');

		// elements
		this.pipe = null;
		this.sound = null;
		this.sound2 = null;

		// on receive and send events
		this.onRaw('data', this._get);
		this.onCmd('send', this._set);

		// on stealth
		this.onRaw('stealth', function(raw, pipe) {
			// put online / offline
			if(raw.data.status == 'online') this._online(raw.data);
			else this._offline(raw.data);

			// save stealth
			if(this.options.name == raw.data.name)
				Online.Data.shadow = raw.data.shadow;
		});

		// on error
		this.onError('004', this._reset);
		this.onError('006', this._error);
		this.onError('007', this._error);

		// on load, ready and pipe events
		this.addEvent('load', this._load);
		this.addEvent('ready', this._ready);
		this.addEvent('multiPipeCreate', this._pipe);

		// user events
		this.addEvent('userJoin', function(user, pipe) {
			this._online(user.properties);
		});
		this.addEvent('userLeft', function(user, pipe) {
			this._offline(user.properties);
		});
	},

	// get
	_get: function(raw, pipe) {
		// write message in box
		this._write(pipe, raw.data.msg, raw.data.from, true);

	// set
	}, _set: function(data, pipe) {
		// write message in box
		this._write(pipe, data.msg, this.core.user);

	}, _write: function(pipe, msg, from, reverse) {
		var timestamp = dateFormat(new Date(), "h:MM TT"),
			split = this._decode(msg).split('9e10e'),
			to = split[0],
			dest = reverse ? from.properties.name : split[0],
			user = from.properties.name,
			text = this._parse(split[1]);

		// when receiving
		if(reverse) {
			// make sure we're actually talking to the correct person
			if(this.options.name != to)
				return false;

			// only show notifications if window is not focused
			if(Online.Data.focus === false) {
				// notify the browser window / tab
				jQuery.titleAlert(this.translate('new'), {
					requireBlur: false,
					stopOnFocus: true,
					duration: 0,
					interval: 1000
				});
			}

			// notify by sound (if enabled)
			if(Online.cg('ifm_online').sounds) {
				this.sound.play();
				this.sound2.src = this.sound2.title;
			}

			// mark message as read
			Online.aj({
				url: 'site/read',
				type: 'post',
				data: {from: dest, to: this.options.name, msg: msg},
				cache: false,
				success: function(x) {
					// nothing here
				}
			});

			// open chat to the other guy
			Online.Dock.open('user_'+ dest, 2);

		// when sending
		} else {
			// save message in log
			Online.aj({
				url: 'site/log',
				type: 'post',
				data: {from: this.options.name, to: to, msg: msg},
				cache: false,
				success: function(x) {
					// nothing here
				}
			});
		}

		// get user info
		var user = Online.Data().find(user),
			body = $$('#box_'+ dest, this.dock).find('div.body');

		// create last message holder
		if(typeof pipe.lastMsg == 'undefined')
			pipe.lastMsg = {};

		// append message to last message
		if(pipe.lastMsg[dest] && pipe.lastMsg[dest].from.pubid == from.pubid) {
			// write in dom
			pipe.lastMsg[dest].mark.find('div.message').append(
				'<span>' + text + '</span><dfn title="08/26/12 15:02">' + timestamp + '</dfn>'+
				'<div class="clear"></div>'
			);

		// create new one
		} else {
			// create dom element
			var mark = $$(
				'<div class="user">'+
					'<table class="user_'+ user.id +'">'+
						'<colgroup><col width="1%"><col></colgroup>'+
						'<tr>'+
							'<td valign="top" style="vertical-align: top;">'+
								'<div class="avatar"><img src="'+ user.avatar +'"></div>'+
							'</td>'+
							'<td valign="top" style="vertical-align: top; padding-top: 3px;">'+
								'<span class="user'+ (this.options.name == user.id ? ' me' : '') +'">'+ user.name +'</span>'+
								'<div class="clear"></div>'+
								'<div class="top"></div>'+
								'<div class="message">'+
									'<span>'+ text +'</span><dfn title="08/26/12 15:02">'+ timestamp +'</dfn>'+
									'<div class="clear"></div>'+
								'</div>'+
							'</td>'+
						'</tr>'+
					'</table>'+
				'</div>');

			// write in dom
			body.find('div.inside').append(mark);

			// save last message
			pipe.lastMsg[dest] = {from: from, mark: mark};
		}

		// reinit scroll
		this._scroll(body, 'bottom');

	// write history
	}, _history: function(data, pipe, to) {
		// define this var here
		var lastMsg = null;

		// clear messages
		$$('#box_'+ to, this.dock).find('div.user').remove();

		// get some vars
		var	self = this,
			body = $$('#box_'+ to, this.dock).find('div.body');

		// no data? skip
		if(!data.length > 0)
			return this._scroll(body, false);

		// go through each message and inject them into message container
		$$.each(data, function(k, v) {
			var split = self._decode(v.message).split('9e10e'),
				text = self._parse(split[1]);

			// append message to last message
			if(lastMsg && lastMsg.from.from_id == v.from_id) {
				// write in dom
				lastMsg.mark.find('div.message').append(
					'<span>'+ text +'</span><dfn title="'+ v.date_created +'">'+ v.time_created +'</dfn>'+
					'<div class="clear"></div>'
				);

			// create new one
			} else {
				// create dom element
				var mark = $$(
					'<div class="user">'+
						'<table class="user_'+ v.from_id +'">'+
							'<colgroup><col width="1%"><col></colgroup>'+
							'<tr>'+
								'<td valign="top" style="vertical-align: top;">'+
									'<div class="avatar"><img src="'+ v.from_avatar +'"></div>'+
								'</td>'+
								'<td valign="top" style="vertical-align: top; padding-top: 3px;">'+
									'<span class="user'+ (self.options.name == v.from_id ? ' me' : '') +'">'+ v.from_name +'</span>'+
									'<div class="clear"></div>'+
									'<div class="top"></div>'+
									'<div class="message">'+
										'<span>'+ text +'</span><dfn title="'+ v.date_created +'">'+ v.time_created +'</dfn>'+
										'<div class="clear"></div>'+
									'</div>'+
								'</td>'+
							'</tr>'+
						'</table>'+
					'</div>');

				// write in dom
				body.find('div.inside').append(mark);

				// save last message
				lastMsg = {from: v, mark: mark};
			}
		});

		// inject delimiter
		body.find('div.inside').append('<div class="user"><div class="delimiter"></div></div>');

		// reset last msg
		if(pipe.lastMsg)
			delete pipe.lastMsg[to];

		// perform scroll
		this._scroll(body, 'bottom');

	// parse message
	}, _parse: function(message) {
		// htmlentities
		message = message
			.replace(/&/g, "&amp;")
			.replace(/</g, "&lt;")
			.replace(/>/g, "&gt;")
			.replace(/"/g, "&quot;")
			.replace(/'/g, "&#039;");

		// linkify message
		var self = this;
		message = linkify(message, {'callback': function(text, href) {
			return href ? '<a target="_blank" href="' + href + '" title="' + href + '">' + self._truncate(text, 70) + '<\/a>' : text;
		}});

		// smilify message
		message = this._emoticons(message);

		// return the message
		return message;

	// truncate string
	}, _truncate: function(fullStr, strLen, separator) {
		if (fullStr.length <= strLen)
			return fullStr;

		separator = separator || '&hellip;';

		var sepLen = separator.length,
			charsToShow = strLen - sepLen,
			frontChars = Math.ceil(charsToShow / 2),
			backChars = Math.floor(charsToShow / 2);

		return fullStr.substr(0, frontChars) + separator + fullStr.substr(fullStr.length - backChars);

	// handle emoticons
	}, _emoticons: function(text) {
		var emoticons = {
			// extra
			':))' : '-48px 0px', ':-))' : '-48px 0px',
			':((' : '-160px 0px', ':-((' : '-160px 0px',

			// regular
			':)' : '0px 0px', ':-)' : '0px 0px',
			':(' : '-16px 0px', ':-(' : '-16px 0px',
			':P' : '-32px 0px', ':-P' : '-32px 0px', ':p' : '-32px 0px', ':-p' : '-32px 0px',
			':D' : '-48px 0px', ':-D' : '-48px 0px', ':d' : '-48px 0px', ':-d' : '-48px 0px',
			':O' : '-64px 0px', ':-O' : '-64px 0px', ':o' : '-64px 0px', ':-o' : '-64px 0px',
			';)' : '-80px 0px', ';-)' : '-80px 0px',

			// love
			':*' : '-208px 0px', ':-*': '-208px 0px',
			'&lt;3': '-224px 0px'
		}, patterns = [], metachars = /[[\]{}()*+?.\\|^$\-,&#\s]/g;

		// build a regex pattern for each defined property
		for (var i in emoticons)
			if (emoticons.hasOwnProperty(i)) // escape metacharacters
				patterns.push('('+i.replace(metachars, "\\$&")+')');

		// build the regular expression and replace
		return text.replace(new RegExp(patterns.join('|'),'g'), function (match) {
			return typeof emoticons[match] != 'undefined' ?
				'<img title="'+ match +'" class="emote" src="/images/blank.gif" style="background-position: '+ emoticons[match] +'; " />' :
				match;
		});

	// perform scrolling
	}, _scroll: function(container, scroll) {
		// create scroll
		if(typeof scroll == 'undefined')
			return container.mCustomScrollbar({
				scrollInertia: 0
			});

		// only scroll or update when box is open
		if(!container.closest('div.box').hasClass('open'))
			return false;

		// update
		container.mCustomScrollbar("update");

		// scroll to ?
		if(scroll != false)
			// only scroll to if we have a scroll bar
			if(!(container.find('div.mCS_no_scrollbar').length > 0))
				// don't scroll if we're already dragging
				if(!(container.find('div.ui-draggable-dragging').length > 0))
					// perform scroll
					container.mCustomScrollbar("scrollTo", scroll);

	// on bad session reset it
	}, _reset: function() {
		this.core.clearSession();
		this.core.initialize(this.core.options);

	// on error
	}, _error: function(error) {
		var self = this;

		$$('#sidebar_infos', this.sidebar).find('span').removeClass().addClass('error');
		$$('#sidebar_overlay', this.sidebar).animate({opacity: 1}, 'fast', function(){
			$$('#sidebar_overlay', this.sidebar).html('<span class="msg">'+ self.translate('error_msg') +'</span>');
		});

		function __second(start) {
			$$('#sidebar_infos', self.sidebar).find('span').html(
				'<img src="/images/online/status-off.png" />'+ self.translate('error').replace('%s', start)
			);

			start--;
			if(start != -1) {
				window.setTimeout(function() {
					__second(start);
				}, 1000);
			} else self._load();
		};

		__second(15);

	// on load
	}, _load: function(a) {
		var self = this;

		// mark as signing
		$$('#sidebar_infos', this.sidebar).find('span').removeClass().addClass('signing');
		$$('#sidebar_infos', this.sidebar).find('span').html(
			'<img src="/images/online/signing.gif" />'+ this.translate(this.options.status == 'online' ? 'signing' : 'stealthy')
		);

		// initiate ape connection
		window.setTimeout(function(){
			self.core.start({
				'name': self.options.name,
				'status': self.options.status,
				'shadow': self.options.shadow
			});
		}, 100);

	// on ready
	}, _ready: function() {
		// inject sound
		this.sound = new Element('audio', {'src': '/sounds/chat.wav', 'preload': 'auto', 'autobuffer': ''});
		this.sound.inject(this.dock.get(0));

		// ie sound
		this.sound2 = new Element('bgsound', {'title': '/sounds/chat.wav'});
		this.sound2.inject(this.dock.get(0));

		// mark as online
		if(this.options.status == 'online') {
			// switch button
			$$('#ctx_status', this.sidebar).removeClass().addClass('offline');
			$$('#ctx_status', this.sidebar).html(this.translate('go_offline'));

			// mark as online
			$$('#sidebar_infos', this.sidebar).find('span').removeClass().addClass('online');
			$$('#sidebar_infos', this.sidebar).find('span').html(
				'<img src="/images/online/status-on.png" />'+ this.translate('online')
			);

		// mark as offline
		} else {
			// switch button
			$$('#ctx_status', this.sidebar).removeClass().addClass('online');
			$$('#ctx_status', this.sidebar).html(this.translate('go_online'));

			// mark as offline (invisible)
			$$('#sidebar_infos', this.sidebar).find('span').removeClass().addClass('offline');
			$$('#sidebar_infos', this.sidebar).find('span').html(
				'<img src="/images/online/status-off.png" />'+ this.translate('invisible')
			);
		}

		// hide overlay
		$$('#sidebar_overlay', this.sidebar).fadeOut('fast', function(){
			$$(this).remove();
		});

		// unlock status button
		Online.Stealth.unlock();

		// init dock
		Online.Dock.init();

	// create pipe
	}, _pipe: function(pipe) {
		// assign vars and pipe
		var self = this;
		this.pipe = pipe;

		// bind history
		this.dock.delegate('div.history a', 'click', function(e) {
			self._loadHistory(e, pipe);
		});

		// bind input event
		this.dock.delegate('div.box input', 'keydown', function(e){
			// get vars
			var code = (e.keyCode ? e.keyCode : e.which),
				i = $$(this, self.dock);
				u = i.closest('div.box').attr('id').replace('box_', '');

			// on enter
			if(code == 13) {
				// trim
				i.val(i.val().trim());

				// empty?
				if(i.val().length == 0)
					return false;

				// save
				pipe.send(self._encode(u +'9e10e'+ i.val()));
				i.val('');
			}
		});

		// set shadow
		Online.Data.shadow = this.core.user.properties.shadow;

	// load history
	}, _loadHistory: function(e, pipe) {
		// based on class load what we need
		var a = $$(e.currentTarget, this.dock),
			h = a.removeClass('active').attr("class"),
			s = a.closest('span'),
			to = a.closest('div.box').attr('id').replace('box_', '');

		// already loading? stop it right there buster
		if(s.hasClass('loading'))
			return false;

		// unbold all
		s.find('a').removeClass('active');

		// show loading
		s.addClass('loading');

		// perform ajax
		var self = this;
		Online.aj({
			url: 'site/get',
			type: 'post',
			data: {from: this.options.name, to: to, history: h},
			cache: false,
			success: function (x) {
				s.removeClass('loading');
				a.addClass('active');

				self._history(x.history, pipe, to);
			}
		});

	// update status in data
	}, _updateStatus: function(status, user) {
		// change status
		$$.each(Online.Data.buddies, function(k, v){
			if(v.id == user)
				v.status = status;
		});

		// change status in providers as well
		$$.each(Online.Data.providers, function(k, v){
			if(v.id == user)
				v.status = status;
		});

		// update list
		Online.Sidebar.update();

	// on user online
	}, _online: function(user) {
		// user is offline? then skip
		if(user.status == 'offline')
			return false;

		// get shadow
		var shadow = JSON.parse(decodeURIComponent(user.shadow)),
			found = false,
			self = this;

		// go through each shadowy figure
		$$.each(shadow, function(k, v){
			if(v == self.options.name) {
				found = true;
				return false;
			}
		});

		// if we are found in his shadow list go offline
		if(found)
			return this._offline(user);

		// save status in data
		this._updateStatus('online', user.name);

		// save status in dom
		$$('#sidebar_users div.inside div.user', this.sidebar).find('table[class="user_'+ user.name +'"]').find('div.offline').removeClass().addClass('online');

	// on user offline
	}, _offline: function(user) {
		// save status in data
		this._updateStatus('offline', user.name);

		// save status in dom
		$$('#sidebar_users div.inside div.user', this.sidebar).find('table[class="user_'+ user.name +'"]').find('div.online').removeClass().addClass('offline');

	// encode the message being sent
	}, _encode: function(msg) {
		return Base64.encode(msg).replace('==', '1e2e').replace('=', '3e4e').replace('+', '5e6e').replace('/', '7e8e');

	// decode the message being received
	}, _decode: function(msg) {
		return Base64.decode(msg.replace('1e2e', '==').replace('3e4e', '=').replace('5e6e', '+').replace('7e8e', '/'));

	// translate
	}, translate: function(key) {
		return Online.Translate.init(key);
	}
});